<?php
require_once('Zend/Json.php');
require_once('json_class.inc');

/**
 JSONDealer
*/
class JSONDealer {
  protected $_type;            //menu, topic, question
  protected $_node;            //current point
  protected $_parent;          //parent
  protected $_error = '';      //error information
  protected $_source = '';     //link file source directory
  protected $_zipped = false;  //string zipped or not
  protected $_data = array();

  protected $_cache = array();
  protected $_resources = array();

  public function __construct($str='') {
    if(is_string($str)) {
      //JSON text
      $this->decode($str);
    } else if (is_object($str) && get_class($str) == 'JSONDealer') {
      //JSON Dealer object
      $this->_data = $str->toArray();
      $this->_node = &$this->_data;
    } else if (is_array($str)){
      $this->_data = $str;
      $this->_node = &$this->_data;
    }
  }

  public function __set($key, $value) {
    $node = &$this->_node;  
    if (isset($node['type']) && isset($node['data'])) {
      //if not top level then go to data level
      $node = &$node['data'];
    }
    if (is_object($value) && is_a($value, 'JSONDealer')) {
      $node[$key] = $value->toArray();
    } else {
      //hook up process when file resource changed
      if (isset($node[$key]) && is_string($node[$key]) && $this->isFile($node[$key])) {
        $change = array();
        $change['src'] = $node[$key];
        $change['dst'] = $value;
        $this->addFileChange($change);
      }
      $node[$key] = $value;
    }
      //throw new ReException("Unknown key: $key");
  }

  public function __get($key) {
    $node = $this->_node;  
    $rtn = null;

    if (isset($node['type']) && isset($node['data'])) {
      $node = &$node['data'];
    }
    if (!isset($node[$key])) {
      if (isset($node['data'])) {
        $node = &$node['data'];
      }
    }
    //root is the top level
    if ($key == 'root') {
      return new JSONDealer($this->_data);
    }
    if ($key == 'parent') {
      return new JSONDealer($this->_parent);
    }
    if (isset($node[$key])) {
      //$this->_node = $node;
      $node = &$node[$key];
      if (is_string($node)) {
        $rtn = $node;
      } else if (is_numeric($node)) {
        $rtn = $node;
      } else if ($node === true || $node === false) {
        // boolean        
        $rtn = $node;
      } else {
        $rtn = new JSONDealer($node);
      }
    }
    return $rtn; 
  }

  public function showDisplayWithHeader() {
    header('Content-type: application/json');
    if (!$this->_zipped) {
      print $this->encode();
    } else {
      print base64_encode(gzcompress($this->encode()));
    }
  }

  public function showZippedWithHeader() {
    header('Content-type: application/json');
    print base64_encode(gzcompress($this->encode()));
  }

  public function setZipped() {
    $this->_zipped = true;
  }

  public function encode() {
    $json_str = Zend_Json::encode($this->_data);

    // for debug
    /*
    $o = LSProductFactory::create();
    $fp = fopen("/tmp/mc_" . $o->config->member_course_id . ".json", "a");
    
    if (isset($_SERVER['REQUEST_URI'])) {
      $url = $_SERVER['REQUEST_URI'];
    } else {
      $url = '';
    }
    
    fwrite($fp,  "SEND to client: ----------------\n" . 
           "URL: {$url}\n" .
           $json_str . "\n\n");
    fclose($fp);
    */
    return $json_str;
  }

  static public function getInstance() {
    $str = '{"data":{},"type":""}';
    $rtn = new JSONDealer($str);
    return $rtn;
  }

  static public function create() {
    $str = '{"data":{},"type":""}';
    $rtn = new JSONDealer($str);
    return $rtn;
  }

  public function setType($type) {
    $this->_data['type'] = $type;
  }

  public function getType() {
    return $this->_data['type'];
  }

  public function getCount() {
    if (is_array($this->_node)) {
      return count($this->_node);
    }
    return 0;
  }
  
  public function decode($str) {
    $this->_data = Zend_Json::decode($str);
    if (!is_array($this->_data)) {
      $this->_error = $str .' is not json string';
    }
    $this->_node = &$this->_data;
    return $this->_data;
  }

  public function &getCurrent() {
    return $this->_node;
  }

  public function cloneNode() {
  }

  public function cacheNode($id, $node) {
    $this->_cache[$id] = $node;
  }

  public function pickNode($id) {
    return $this->_cache[$id];
  }

  public function resetCurrent(&$cur=null) {
    //pass the reference instead of copy
    //node is always a poiter
    if (isset($cur)) {
      $this->_node = &$cur;
    } else {
      $this->_node = &$this->_data;
    }
  }

  public function encodeForDisplay() {
    return $this->encode();
  }

  public function getError() {
    return $this->_error;
  }
  
  public function locateById($id) {
    if (isset($this->_node['data']) && isset($this->_node['type'])) {
        $this->_parent = $this->_node;
        $this->_node = &$this->_node['data'];
    } else if (isset($this->_node['data']) && isset($this->_node['link'])) {
      //load the dta and fill into the array
      if (strlen($this->_source) > 0) {
        $file_name = Util::fixPath($this->_source . '/' . $this->_node['link']);
        if (is_readable($file_name)) {
          $buf = file_get_contents($file_name);
        } else {
          throw new FileNotFoundException($file_name);
        }
        $this->_node = Zend_Json::decode($buf);
      }
    }

    if (isset($this->_node[$id])) {
      $this->_parent = $this->_node;
      $this->_node = &$this->_node[$id];
      return true;
    } else {
      //throw new REException("$id not found when locateById");
    }
    return false;
  }

  public function toArray() {
    return $this->_data;
  }

  public function setSource($src) {
    $this->_source = $src;
  }

  public function updatePictureResource($callback) {
    $pics = $this->updateType(JSONClass::PictureResource, &$this->_data, $callback);
    foreach($pics as $pic) {
      if ($pic['src']->resourceID) {
        $change = array();
        $change['src'] = $pic['src']->resourceID;
        $change['dst'] = $pic['dst']->resourceID;
        $this->addFileChange($change);
      }
    }
    return $pics;
  }

  public function updatePopupResource($callback) {
    $pops = $this->updateType(JSONClass::PopupResource, &$this->_data, $callback);
    foreach($pops as $pop) {
      if ($pop['src']->resourceID) {
        $change = array();
        $change['src'] = $pop['src']->resourceID;
        $change['dst'] = $pop['dst']->resourceID;
        $this->addFileChange($change);
      }
    }
    return $pops;
  }

  public function updateSoundResource($callback) {
    $sounds = $this->updateType(JSONClass::SoundResource, &$this->_data, $callback);
    foreach($sounds as $sound) {
      if ($sound['src']->resourceID) {
        $change = array();
        $change['src'] = $sound['src']->resourceID;
        $change['dst'] = $sound['dst']->resourceID;
        $this->addFileChange($change);
      }
    }
    return $sounds;
  }

  public function findSoundResource() {
    $sounds = $this->findType(JSONClass::SoundResource, $this->_data);
    return $sounds;
  }

  public function findBinaryResource() {
    $bins = $this->findType(JSONClass::BinaryResource, $this->_data);
    return $bins;
  }

  public function findPopupResource() {
    $pops = $this->findType(JSONClass::PopupResource, $this->_data);
    return $pops;
  }

  public function findPictureResource() {
    $pictures = $this->findType(JSONClass::PictureResource, $this->_data);
    return $pictures;
  }

  public function findSwfResource() {
    $swfs = $this->findType(JSONClass::SwfResource, $this->_data);
    return $swfs;
  }

  public function getChangedFiles() {
    return $this->_resources;
  }

  protected function isFile($name) {
    //mp3/jpg/pdf files
    if (strpos($name, '.mp3') > 0 ||
        strpos($name, '.jpg') > 0 ||
        strpos($name, '.pdf') > 0) {
          return true;
        }
    return false;
  }

  //find nodes by Name
  protected function findName($name, $node) {
    $fnd = array();
    if (is_array($node)) {
      foreach($node as $key => $val) {
        if (strcmp($key, $name) == 0) {
          if (!$val) {
            $fnd[] = null;
          } else if (is_string($val)) {
            $fnd[] = $val;
          } else {
            $fnd[] = new JSONDealer($val);
          }
        } else if (is_array($val)) {
          if (isset($val['type']) && isset($val['data'])) {
            $rtn = $this->findName($name, $val['data']);
          } else {
            $rtn = $this->findName($name, $val);
            foreach($rtn as $item) {
              $fnd[] = $item;
            }
          }
        }
      }
    }
    return $fnd;
  }

  //find nodes by Type
  protected function updateType($type, $node, $callback=null) {
    $fnds = array();
    if (is_array($node)) {
     
      foreach($node as $key => $val) {
        if (is_array($val)) {
          if (isset($val['type']) && isset($val['data'])) {
            if ($val['type'] == $type) {
              $fnd = array();
              $fnd['src'] = new JSONDealer($val);
              $callback(&$node[$key]);
              $fnd['dst'] = new JSONDealer($node[$key]);
              $fnds[] = $fnd;
            } else {
              $rtn = $this->updateType($type, &$node[$key]['data'], $callback);
              foreach($rtn as $item) {
                $fnds[] = $item;
              }
            }
          } else {
            $rtn = $this->updateType($type, &$node[$key], $callback);
            foreach($rtn as $item) {
              $fnds[] = $item;
            }
          }
        } else {
        }
      }
    }
    return $fnds;
  }
  //find nodes by Type
  protected function findType($type, $node) {
    $fnd = array();
    if (is_array($node)) {
     
      foreach($node as $key => $val) {
        if (is_array($val)) {
          if (isset($val['type']) && isset($val['data'])) {
            if ($val['type'] == $type) {
              $fnd[] = new JSONDealer($val);
            } else {
              $rtn = $this->findType($type, $val['data']);
              foreach($rtn as $item) {
                $fnd[] = $item;
              }
            }
          } else {
            $rtn = $this->findType($type, $val);
            foreach($rtn as $item) {
              $fnd[] = $item;
            }
          }
        } else {
        }
      }
    }
    return $fnd;
  }
  //find nodes by Type
  protected function nodeWalk($node, $processor) {
    if (is_array($node)) {
      foreach($node as $key => $val) {
        if (is_array($val)) {
          if (isset($val['type']) && isset($val['data'])) {
            if ($val['type'] == $processor->type) {
              $processor->process(new JSONDealer($val));
            } else {
              $this->nodeWalk($val, $processor);

            }
          } else {
            $this->nodeWalk($val, $processor);
          }
        } else {
        }
      }
    }
  }

  //put changes to file list
  protected function addFileChange($change) {
    if (($key = array_search($change['src'], $this->_resources)) !== FALSE) {
      $this->_resources[$key] = $change['dst'];
    } else {
      $this->_resources[$change['src']] = $change['dst'];
    }
  }

}

?>
